///*
// * Copyright 2004-2020 H2 Group. Multiple-Licensed under the MPL 2.0,
// * and the EPL 1.0 (https://h2database.com/html/license.html).
// * Initial Developer: H2 Group
// */
//package org.h2.index;
//
//import java.util.ArrayList;
//import org.h2.api.ErrorCode;
//import org.h2.command.dml.AllColumnsForPlan;
//import org.h2.engine.Constants;
//import org.h2.engine.DbObject;
//import org.h2.engine.Session;
//import org.h2.message.DbException;
//import org.h2.message.Trace;
//import org.h2.result.RowFactory;
//import org.h2.result.SearchRow;
//import org.h2.result.SortOrder;
//import org.h2.schema.SchemaObjectBase;
//import org.h2.table.Column;
//import org.h2.table.IndexColumn;
//import org.h2.table.Table;
//import org.h2.table.TableFilter;
//import org.h2.util.StringUtils;
//import org.h2.value.DataType;
//import org.h2.value.Value;
//import org.h2.value.ValueNull;
//
///**
// * Most index implementations extend the base index.
// */
//public abstract class BaseIndex extends SchemaObjectBase implements Index {
//
//    protected IndexColumn[] indexColumns;
//    protected Column[] columns;
//    protected int[] columnIds;
//    protected final Table table;
//    protected final IndexType indexType;
//    private final RowFactory rowFactory;
//
//    /**
//     * Initialize the base index.
//     *
//     * @param newTable the table
//     * @param id the object id
//     * @param name the index name
//     * @param newIndexColumns the columns that are indexed or null if this is
//     *            not yet known
//     * @param newIndexType the index type
//     */
//    protected BaseIndex(Table newTable, int id, String name,
//            IndexColumn[] newIndexColumns, IndexType newIndexType) {
//        super(newTable.getSchema(), id, name, Trace.INDEX);
//        this.indexType = newIndexType;
//        this.table = newTable;
//        if (newIndexColumns != null) {
//            this.indexColumns = newIndexColumns;
//            columns = new Column[newIndexColumns.length];
//            int len = columns.length;
//            columnIds = new int[len];
//            for (int i = 0; i < len; i++) {
//                Column col = newIndexColumns[i].column;
//                columns[i] = col;
//                columnIds[i] = col.getColumnId();
//            }
//        }
//        rowFactory = database.getRowFactory().createRowFactory(
//                database, database.getCompareMode(), database.getMode(),
//                database, table.getColumns(),
//                newIndexType.isScan() ? null : newIndexColumns);
//    }
//
//    @Override
//    public RowFactory getRowFactory() {
//        return rowFactory;
//    }
//
//    /**
//     * Check that the index columns are not CLOB or BLOB.
//     *
//     * @param columns the columns
//     */
//    protected static void checkIndexColumnTypes(IndexColumn[] columns) {
//        for (IndexColumn c : columns) {
//            if (!DataType.isIndexable(c.column.getType())) {
//                throw DbException.getUnsupportedException(
//                        "Index on column: " + c.column.getCreateSQL());
//            }
//        }
//    }
//
//    /**
//     * Create a duplicate key exception with a message that contains the index
//     * name.
//     *
//     * @param key the key values
//     * @return the exception
//     */
//    public DbException getDuplicateKeyException(String key) {
//        StringBuilder builder = new StringBuilder();
//        getSQL(builder, false).append(" ON ");
//        table.getSQL(builder, false).append('(');
//        builder.append(getColumnListSQL(false));
//        builder.append(')');
//        if (key != null) {
//            builder.append(" VALUES ").append(key);
//        }
//        DbException e = DbException.get(ErrorCode.DUPLICATE_KEY_1, builder.toString());
//        e.setSource(this);
//        return e;
//    }
//
//    /**
//     * Get "PRIMARY KEY ON <table> [(column)]".
//     *
//     * @param mainIndexColumn the column index
//     * @return the message
//     */
//    protected StringBuilder getDuplicatePrimaryKeyMessage(int mainIndexColumn) {
//        StringBuilder builder = new StringBuilder("PRIMARY KEY ON ");
//        table.getSQL(builder, false);
//        if (mainIndexColumn >= 0 && mainIndexColumn < indexColumns.length) {
//            builder.append('(');
//            indexColumns[mainIndexColumn].getSQL(builder, false).append(')');
//        }
//        return builder;
//    }
//
//    @Override
//    public void removeChildrenAndResources(Session session) {
//        table.removeIndex(this);
//        remove(session);
//        database.removeMeta(session, getId());
//    }
//
//    /**
//     * Calculate the cost for the given mask as if this index was a typical
//     * b-tree range index. This is the estimated cost required to search one
//     * row, and then iterate over the given number of rows.
//     *
//     * @param masks the IndexCondition search masks, one for each column in the
//     *            table
//     * @param rowCount the number of rows in the index
//     * @param filters all joined table filters
//     * @param filter the current table filter index
//     * @param sortOrder the sort order
//     * @param isScanIndex whether this is a "table scan" index
//     * @param allColumnsSet the set of all columns
//     * @return the estimated cost
//     */
////<<<<<<< HEAD
////    //子类MVSpatialIndex、SpatialTreeIndex覆盖了此方法
////    //代价的计算总体上是围绕行数进行的
////    protected long getCostRangeIndex(int[] masks, long rowCount, TableFilter filter, SortOrder sortOrder) {
////        rowCount += Constants.COST_ROW_OFFSET; //为什么加1000，见MVPrimaryIndex.getCost中的注释或COST_ROW_OFFSET的注释
////        long cost = rowCount;
////        long rows = rowCount;
////        int totalSelectivity = 0;
////        if (masks == null) {
////            return cost;
////        }
////        //masks代表基表所有字段中的那一些用于查询条件了，0表示没有用到
////        //columns是索引字段
////        for (int i = 0, len = columns.length; i < len; i++) {
////            Column column = columns[i];
////            int index = column.getColumnId();
////            int mask = masks[index];
////            //代价比较:
////            //EQUALITY < RANGE < END < START
////            //如果索引字段列表的第一个字段在Where中是RANGE、START、END，那么索引字段列表中的其他字段就不需要再计算cost了，
////            //如果是EQUALITY，则还可以继续计算cost，rows变量的值会变小，cost也会变小
////            //这里为什么不直接用(mask == IndexCondition.EQUALITY)？
////            //因为id=40 AND id>30会生成两个索引条件，
////            //在org.h2.table.TableFilter.getBestPlanItem中合成一个mask为3(IndexCondition.EQUALITY|IndexCondition.START)
////            if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) {
////            	//索引字段列表中的最后一个在where当中是EQUALITY，且此索引是唯一索引时，cost直接是3
////            	//因为如果最后一个索引字段是EQUALITY，说明前面的字段全是EQUALITY，
////            	//如果是唯一索引则rowCount / distinctRows是1，所以rows = Math.max(rowCount / distinctRows, 1)=1
////            	//所以cost = 2 + rows = 3
////                if (i == columns.length - 1 && getIndexType().isUnique()) {
////                    cost = 3;
////                    break;
////                }
////                totalSelectivity = 100 - ((100 - totalSelectivity) * (100 - column.getSelectivity()) / 100);
////                long distinctRows = rowCount * totalSelectivity / 100; //totalSelectivity变大时distinctRows变大
////                if (distinctRows <= 0) {
////                    distinctRows = 1;
////                }
////                rows = Math.max(rowCount / distinctRows, 1); //distinctRows变大，则rowCount / distinctRows变小，rows也变小
////                cost = 2 + rows; //rows也变小，所以cost也变小
////            } else if ((mask & IndexCondition.RANGE) == IndexCondition.RANGE) { //见TableFilter.getBestPlanItem中的注释
////                cost = 2 + rows / 4; //rows开始时加了1000，所以rows / 4总是大于1的
////                break;
////            } else if ((mask & IndexCondition.START) == IndexCondition.START) {
////                cost = 2 + rows / 3;
////                break;
////            } else if ((mask & IndexCondition.END) == IndexCondition.END) { //"<="的代价要小于">="
////                cost = rows / 3;
////                break;
////            } else {
////                break;
////            }
////        }
////        // if the ORDER BY clause matches the ordering of this index,
////        // it will be cheaper than another index, so adjust the cost accordingly
////        //order by中的字段和排序方式与索引字段相同时，cost再减去排序字段个数
////        //注意：排序字段个数不管比索引字段个数多还是少都是没问题的，这里只是尽量匹配
////=======
//    protected final long getCostRangeIndex(int[] masks, long rowCount,
//            TableFilter[] filters, int filter, SortOrder sortOrder,
//            boolean isScanIndex, AllColumnsForPlan allColumnsSet) {
//        rowCount += Constants.COST_ROW_OFFSET;
//        int totalSelectivity = 0;
//        long rowsCost = rowCount;
//        if (masks != null) {
//            int i = 0, len = columns.length;
//            boolean tryAdditional = false;
//            while (i < len) {
//                Column column = columns[i++];
//                int index = column.getColumnId();
//                int mask = masks[index];
//                if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) {
//                    if (i == len && getIndexType().isUnique()) {
//                        rowsCost = 3;
//                        break;
//                    }
//                    totalSelectivity = 100 - ((100 - totalSelectivity) *
//                            (100 - column.getSelectivity()) / 100);
//                    long distinctRows = rowCount * totalSelectivity / 100;
//                    if (distinctRows <= 0) {
//                        distinctRows = 1;
//                    }
//                    rowsCost = 2 + Math.max(rowCount / distinctRows, 1);
//                } else if ((mask & IndexCondition.RANGE) == IndexCondition.RANGE) {
//                    rowsCost = 2 + rowsCost / 4;
//                    tryAdditional = true;
//                    break;
//                } else if ((mask & IndexCondition.START) == IndexCondition.START) {
//                    rowsCost = 2 + rowsCost / 3;
//                    tryAdditional = true;
//                    break;
//                } else if ((mask & IndexCondition.END) == IndexCondition.END) {
//                    rowsCost = rowsCost / 3;
//                    tryAdditional = true;
//                    break;
//                } else {
//                    if (mask == 0) {
//                        // Adjust counter of used columns (i)
//                        i--;
//                    }
//                    break;
//                }
//            }
//            // Some additional columns can still be used
//            if (tryAdditional) {
//                while (i < len && masks[columns[i].getColumnId()] != 0) {
//                    i++;
//                    rowsCost--;
//                }
//            }
//            // Increase cost of indexes with additional unused columns
//            rowsCost += len - i;
//        }
//        // If the ORDER BY clause matches the ordering of this index,
//        // it will be cheaper than another index, so adjust the cost
//        // accordingly.
//        long sortingCost = 0;
//        if (sortOrder != null) {
//            sortingCost = 100 + rowCount / 10;
//        }
//        if (sortOrder != null && !isScanIndex) {
//            boolean sortOrderMatches = true;
//            int coveringCount = 0;
//            int[] sortTypes = sortOrder.getSortTypes();
//            TableFilter tableFilter = filters == null ? null : filters[filter];
//            for (int i = 0, len = sortTypes.length; i < len; i++) {
//                if (i >= indexColumns.length) {
//                    // We can still use this index if we are sorting by more
//                    // than it's columns, it's just that the coveringCount
//                    // is lower than with an index that contains
//                    // more of the order by columns.
//                    break;
//                }
//                Column col = sortOrder.getColumn(i, tableFilter);
//                if (col == null) {
//                    sortOrderMatches = false;
//                    break;
//                }
//                IndexColumn indexCol = indexColumns[i];
//                if (!col.equals(indexCol.column)) {
//                    sortOrderMatches = false;
//                    break;
//                }
//                int sortType = sortTypes[i];
//                if (sortType != indexCol.sortType) {
//                    sortOrderMatches = false;
//                    break;
//                }
//                coveringCount++;
//            }
//            if (sortOrderMatches) {
//                // "coveringCount" makes sure that when we have two
//                // or more covering indexes, we choose the one
//                // that covers more.
//                sortingCost = 100 - coveringCount;
//            }
//        }
//        // If we have two indexes with the same cost, and one of the indexes can
//        // satisfy the query without needing to read from the primary table
//        // (scan index), make that one slightly lower cost.
//        boolean needsToReadFromScanIndex = true;
//        if (!isScanIndex && allColumnsSet != null) {
//            boolean foundAllColumnsWeNeed = true;
//            ArrayList<Column> foundCols = allColumnsSet.get(getTable());
//            if (foundCols != null)
//            {
//                for (Column c : foundCols) {
//                    boolean found = false;
//                    for (Column c2 : columns) {
//                        if (c == c2) {
//                            found = true;
//                            break;
//                        }
//                    }
//                    if (!found) {
//                        foundAllColumnsWeNeed = false;
//                        break;
//                    }
//                }
//            }
//            if (foundAllColumnsWeNeed) {
//                needsToReadFromScanIndex = false;
//            }
//        }
//        long rc;
//        if (isScanIndex) {
//            rc = rowsCost + sortingCost + 20;
//        } else if (needsToReadFromScanIndex) {
//            rc = rowsCost + rowsCost + sortingCost + 20;
//        } else {
//            // The (20-x) calculation makes sure that when we pick a covering
//            // index, we pick the covering index that has the smallest number of
//            // columns (the more columns we have in index - the higher cost).
//            // This is faster because a smaller index will fit into fewer data
//            // blocks.
//            rc = rowsCost + sortingCost + columns.length;
//        }
//        return rc;
//    }
//
//    @Override
//    public int compareRows(SearchRow rowData, SearchRow compare) { //只比较索引字段，并不一定是所有字段
//        if (rowData == compare) {
//            return 0;
//        }
//        for (int i = 0, len = indexColumns.length; i < len; i++) {
//            int index = columnIds[i];
//
//            Value v1 = rowData.getValue(index);
//            Value v2 = compare.getValue(index);
//            //只要compare中有null值就认为无法比较，直接认为rowData和compare相等(通常在查询时在where中再比较)
//            if (v1 == null || v2 == null) {
//                // can't compare further
//                return 0;
//            }
//            int c = compareValues(v1, v2, indexColumns[i].sortType);
//            if (c != 0) {
//                return c;
//            }
//        }
//        return 0;
//    }
//
//    /**
//     * Check if this row may have duplicates with the same indexed values in the
//     * current compatibility mode. Duplicates with {@code NULL} values are
//     * allowed in some modes.
//     *
//     * @param searchRow
//     *            the row to check
//     * @return {@code true} if specified row may have duplicates,
//     *         {@code false otherwise}
//     */
////<<<<<<< HEAD
////    protected boolean containsNullAndAllowMultipleNull(SearchRow newRow) {
////        Mode mode = database.getMode();
////        //1. 对于唯一索引，必须完全唯一，适用于Derby/HSQLDB/MSSQLServer
////        if (mode.uniqueIndexSingleNull) {
////        	//不允许出现:
////        	//(x, null)
////        	//(x, null)
////        	//也不允许出现:
////        	//(null, null)
////        	//(null, null)
////            return false;
////        } else if (mode.uniqueIndexSingleNullExceptAllColumnsAreNull) {
////        	//2. 对于唯一索引，索引记录可以全为null，适用于Oracle
////        	
////        	//不允许出现:
////        	//(x, null)
////        	//(x, null)
////        	//但是允许出现:
////        	//(null, null)
////        	//(null, null)
////=======
//    public boolean mayHaveNullDuplicates(SearchRow searchRow) {
//        switch (database.getMode().uniqueIndexNullsHandling) {
//        case ALLOW_DUPLICATES_WITH_ANY_NULL:
//            for (int index : columnIds) {
//                if (searchRow.getValue(index) == ValueNull.INSTANCE) {
//                    return true;
//                }
//            }
//            return false;
//        case ALLOW_DUPLICATES_WITH_ALL_NULLS:
//            for (int index : columnIds) {
//                if (searchRow.getValue(index) != ValueNull.INSTANCE) {
//                    return false;
//                }
//            }
//            return true;
//        default:
//            return false;
//        }
////<<<<<<< HEAD
////        //3. 对于唯一索引，只要一个为null，就是合法的，适用于REGULAR(即H2)/DB2/MySQL/PostgreSQL
////        
////        //即允许出现:
////    	//(x, null)
////    	//(x, null)
////    	//也允许出现:
////    	//(null, null)
////    	//(null, null)
////        
////        //也就是说，只要相同的两条索引记录包含null即可
////        for (int index : columnIds) {
////            Value v = newRow.getValue(index);
////            if (v == ValueNull.INSTANCE) {
////                return true;
////            }
////        }
////        
////        //4. 对于唯一索引，没有null时是不允许出现两条相同的索引记录的
////        return false;
////=======
////>>>>>>> d9a7cf0dcb563abb69ed313f35cdebfebe544674
//    }
//
//    /**
//     * Compare the positions of two rows.
//     *
//     * @param rowData the first row
//     * @param compare the second row
//     * @return 0 if both rows are equal, -1 if the first row is smaller,
//     *         otherwise 1
//     */
//    public int compareKeys(SearchRow rowData, SearchRow compare) {
//        long k1 = rowData.getKey();
//        long k2 = compare.getKey();
//        if (k1 == k2) {
//            return 0;
//        }
//        return k1 > k2 ? 1 : -1;
//    }
//
//    private int compareValues(Value a, Value b, int sortType) {
//        if (a == b) {
//            return 0;
//        }
//        boolean aNull = a == ValueNull.INSTANCE;
//        boolean bNull = b == ValueNull.INSTANCE;
//        if (aNull || bNull) {
//            return SortOrder.compareNull(aNull, sortType);
//        }
//        int comp = table.compareValues(database, a, b);
//        if ((sortType & SortOrder.DESCENDING) != 0) { //降序时，把比较结果反过来
//            comp = -comp;
//        }
//        return comp;
//    }
//
//    @Override
//    public int getColumnIndex(Column col) { //并不是返回列id，而是索引字段列表中的位置
//        for (int i = 0, len = columns.length; i < len; i++) {
//            if (columns[i].equals(col)) {
//                return i;
//            }
//        }
//        return -1;
//    }
//
//    @Override
//    public boolean isFirstColumn(Column column) {
//        return column.equals(columns[0]);
//    }
//
//    /**
//     * Get the list of columns as a string.
//     *
//     * @param alwaysQuote quote all identifiers
//     * @return the list of columns
//     */
//    private String getColumnListSQL(boolean alwaysQuote) {
//        return IndexColumn.writeColumns(new StringBuilder(), indexColumns, alwaysQuote).toString();
//    }
//
//    @Override
//    public String getCreateSQLForCopy(Table targetTable, String quotedName) {
//        StringBuilder buff = new StringBuilder("CREATE ");
//        buff.append(indexType.getSQL());
//        buff.append(' ');
//        if (table.isHidden()) {
//            buff.append("IF NOT EXISTS ");
//        }
//        buff.append(quotedName);
//        buff.append(" ON ");
//        targetTable.getSQL(buff, true);
//        if (comment != null) {
//            buff.append(" COMMENT ");
//            StringUtils.quoteStringSQL(buff, comment);
//        }
//        buff.append('(').append(getColumnListSQL(true)).append(')');
//        return buff.toString();
//    }
//
//    @Override
//    public String getCreateSQL() {
//        return getCreateSQLForCopy(table, getSQL(true));
//    }
//
//    @Override
//    public IndexColumn[] getIndexColumns() {
//        return indexColumns;
//    }
//
//    @Override
//    public Column[] getColumns() {
//        return columns;
//    }
//
//    @Override
//    public IndexType getIndexType() {
//        return indexType;
//    }
//
//    @Override
//    public int getType() {
//        return DbObject.INDEX;
//    }
//
//    @Override
//    public Table getTable() {
//        return table;
//    }
//
//    @Override
//    public boolean isHidden() {
//        return table.isHidden();
//    }
//}
